require "class"

-- 小殇工具
---@class XSTools
local XSTools = Class(function(self)
    self.desc = "小殇工具核心"
end)

-- 当前对象是否是一个有效实体对象
---@param inst Entity 实体对象
function XSTools:IsEntity(inst)
    if inst and inst.is_a and inst.is_a(ENTITY) then
        if inst:IsValid() then
            return true
        else
            print("<" .. inst.prefab "><" .. inst.name .. "> 该实体已失效或已删除")
            return false
        end
    else
        return false
    end
end

-- 获取实体世界坐标
---@param inst Entity 实体对象, 默认为玩家
function XSTools:GetPosition(inst)
    inst = self:IsEntity(inst) and inst or GetPlayer()
    return Vector3(inst.Transform:GetWorldPosition())
end

-- 判断实体是否有某些标签
---@param inst Entity 实体对象
---@param tags List 待查询的标签名列表
---@param all boolean 该值为true时, 判断实体是否具有要查询的全部标签, 默认拥有其中一个标签即可
function XSTools:HasTags(inst, tags, all)
    if not self:IsEntity(inst) and type(tags) == "list" then
        return false
    end

    if all == true then
        for _, tag in pairs(tags) do
            if not inst:HasTag(tag) then
                return false
            end
        end
        return true
    else
        for _, tag in pairs(tags) do
            if inst:HasTag(tag) then
                return true
            end
        end
        return false
    end
end

-- 获取范围内的实体对象列表
---@param inst Entity 搜索中心实体对象, 默认为玩家
---@param radius number 搜索范围半径
---@param tags table 搜索物品拥有标签过滤列表
---@param exclude_tags table 搜索物品排除标签过滤列表
function XSTools:GetAroundEntities(inst, radius, tags, exclude_tags)
    local x, y, z = self:GetPosition(inst):Get()

    if type(exclude_tags) == "table" then
        exclude_tags = table.merge(exclude_tags, XS_DefaultExcludeTags, false, true)
    else
        exclude_tags = XS_DefaultExcludeTags
    end

    return TheSim:FindEntities(x, y, z, radius, tags, exclude_tags)
end

-- 获取角色身上和背包内的物品(物品未解除关联)
---@param filter_fn function 物品过滤函数, 默认不过滤
function XSTools:GetPlayerItems(filter_fn)
    local entities = {}
    local player_inv = GetPlayer().components.inventory

    -- 角色物品
    table.filter(player_inv.itemslots, filter_fn, entities)

    -- 背包物品
    for _, equip in pairs(player_inv.equipslots) do
        if equip and equip.components.container and equip.components.container.type == "pack" then
            table.filter(equip.components.container.slots, filter_fn, entities)
        end
    end

    return entities
end

-- 获取容器内物品
---@param inst any 容器对象
---@param filter_fn function 物品过滤函数, 默认不过滤
function XSTools:GetContainerItems(inst, filter_fn)
    local entities = {}

    if inst and inst.components and inst.components.container then
        table.filter(inst.components.container.slots, filter_fn, entities)
    end

    return entities
end

-- 召唤指定物至世界上, 召唤物名称, 召唤物预设位置/预设对象, 实体额外处理
function XSTools:Call(prefab, position, fn)
    if Prefabs[prefab] then
        if position == nil then
            position = self:GetPosition()
        end
        if position and position.prefab then
            position = self:GetPosition(position)
        end

        local inst = SpawnPrefab(prefab)

        if position.IsVector3 and position:IsVector3() then
            inst.Transform:SetPosition(position.x, position.y, position.z)
        end

        if type(fn) == "function" then
            fn(inst)
        end
    else
        print("<" .. prefab .. "> 该生物/特效不存在")
    end
end

-- 创建物品, 物品名称, 物品世界位置, 可堆叠物品物品数量
function XSTools:CreatePrefabEnt(prefab, position, stacksize, fn)
    if not Prefabs[prefab] then
        return nil, "小殇专用: 创建实体时发现 <" .. prefab .. "> 该物体不存在"
    end

    stacksize = type(stacksize) == "number" and stacksize >= 1 and math.floor(stacksize) or 1

    local inst = SpawnPrefab(prefab)

    if inst.components.stackable and stacksize > 1 then
        inst.components.stackable:SetStackSize(stacksize)
    end

    if position and position.IsVector3 and position:IsVector3() then
        inst.Transform:SetPosition(position.x, position.y, position.z)
    end

    if type(fn) == "function" then
        fn(inst)
    end

    return inst, nil
end

-- 创建物品数组(单一物品且不止一组), 物品名称, 物品数量, 物品世界位置
function XSTools:CreatePrefabEntity(prefab, ent_num, position)
    local entities = {}

    if Prefabs[prefab] then
        local inst = SpawnPrefab(prefab)
        local maxsize = inst.components.stackable and inst.components.stackable.maxsize or 1
        inst:Remove()

        local stack_num = math.floor(ent_num / maxsize)
        local surplus_num = ent_num % maxsize
        local error_message

        if stack_num > 0 then
            for _ = 1, stack_num do
                inst, error_message = self:CreatePrefabEnt(prefab, position, maxsize)
                if not error_message then
                    table.insert(entities, inst)
                else
                    print(error_message)
                    break
                end
            end
        end

        if surplus_num > 0 then
            inst, error_message = self:CreatePrefabEnt(prefab, position, surplus_num)
            if not error_message then
                table.insert(entities, inst)
            else
                print(error_message)
            end
        end
    else
        print("<" .. prefab .. "> 该物品不存在")
    end

    return entities
end

-- 批量创建物品数组(一次创建多种物品), 物品名称及对应数量表 { 物品名称 = { 物品数量, [物品世界位置] } }
function XSTools:CreatePrefabEntities(prefabs_number_position)
    local entities = {}

    for prefab, number_position in pairs(prefabs_number_position) do
        local number, position
        if type(number_position) == "number" then
            number = number_position
        end
        if type(number_position) == "table" then
            number = number_position[1]
            position = number_position[2]
        end

        ListMerge(entities, self:CreatePrefabEntity(prefab, number, position), true)
    end

    return entities
end

-- 掉落(分散), 参照物, 掉落物名称/掉落物, 掉落物数量, 掉落概率(默认必定掉落), 所有数量均计算概率掉落一次
function XSTools:DropScatter(inst, drop_prefab, number, chance, all_chance)
    number = type(number) == "number" and number or 1 -- 只对掉落物名称生效
    chance = type(chance) == "number" and chance or 1
    if drop_prefab.Transform and drop_prefab.components then
        number = 1
    end

    if chance < 0 then
        return
    end
    if all_chance == true and 0 <= chance and chance < 1 then
        local new_number = 0
        for _ = 1, number do
            if math.random() < chance then
                new_number = new_number + 1
            end
        end
        number = new_number
    end

    if number > 0 then
        -- 掉落物名称
        if type(drop_prefab) == "string" then
            local drops, error_message = self:CreatePrefabEntity(drop_prefab, number, self:GetPosition(inst))
            if type(error_message) ~= "nil" then
                print(error_message)
                return error_message
            end
            for _, drop_inst in pairs(drops) do
                drop_inst.components.inventoryitem:OnDropped(true)
            end
            return
        end

        -- 掉落物实体
        if drop_prefab.Transform and drop_prefab.components then
            drop_prefab.Transform:SetPosition(self:GetPosition(inst):Get())
            drop_prefab.components.inventoryitem:OnDropped(true)
            return
        end
    end
end

-- 掉落(镜头方向), 起始实体, 掉落物名称/实体, 掉落物数量, 掉落概率(默认必定掉落), 所有数量均计算概率掉落一次
function XSTools:DropCamera(inst, drop_prefab, number, chance, all_chance)
    number = type(number) == "number" and number or 1 -- 只对掉落物名称生效
    chance = type(chance) == "number" and chance or 1
    if drop_prefab.Transform and drop_prefab.components then
        number = 1
    end

    if chance < 0 then
        return
    end
    if all_chance == true and 0 <= chance and chance < 1 then
        local new_number = 0
        for _ = 1, number do
            if math.random() < chance then
                new_number = new_number + 1
            end
        end
        number = new_number
    end

    if number > 0 then
        local position = self:GetPosition(inst) + Vector3(0, 1 + math.random(), 0)
        local down = TheCamera:GetDownVec() -- 获取视角之类

        -- 掉落物名称
        if type(drop_prefab) == "string" then
            local drops, error_message = self:CreatePrefabEntity(drop_prefab, position, number)
            if type(error_message) ~= "nil" then
                print(error_message)
                return error_message
            end
            for _, drop_inst in pairs(drops) do
                local angle = math.atan2(down.z, down.x) + (math.random() * 60 - 30) * DEGREES
                local sp = math.random() * 4
                -- 给物品提供初速度，并且遵循重力、摩擦、碰撞等物理规律inst.Physics:SetVel(x,y,z)
                drop_inst.Physics:SetVel(sp * math.cos(angle), math.random() * 2 + 8, sp * math.sin(angle))
                drop_inst.components.inventoryitem:OnStartFalling()
            end
            return
        end

        -- 掉落物实体
        if drop_prefab.Transform and drop_prefab.components then
            drop_prefab.Transform:SetPosition(position:Get())

            local angle = math.atan2(down.z, down.x) + (math.random() * 60 - 30) * DEGREES
            local sp = math.random() * 4
            -- 给物品提供初速度，并且遵循重力、摩擦、碰撞等物理规律inst.Physics:SetVel(x,y,z)
            drop_prefab.Physics:SetVel(sp * math.cos(angle), math.random() * 2 + 8, sp * math.sin(angle))
            drop_prefab.components.inventoryitem:OnStartFalling()
            return
        end
    end
end

-- 掉落(飞天空投), 起始实体, 掉落物名称/实体, 掉落物数量, 掉落概率(默认必定掉落), 所有数量均计算概率掉落一次
function XSTools:DropAbove(inst, drop_prefab, number, chance, all_chance)
    number = type(number) == "number" and number or 1 -- 只对掉落物名称生效
    chance = type(chance) == "number" and chance or 1
    if drop_prefab.Transform and drop_prefab.components then
        number = 1
    end

    if chance < 0 then
        return
    end
    if all_chance == true and 0 <= chance and chance < 1 then
        local new_number = 0
        for _ = 1, number do
            if math.random() < chance then
                new_number = new_number + 1
            end
        end
        number = new_number
    end

    if number > 0 then
        -- 掉落物名称
        if type(drop_prefab) == "string" then
            local drops, error_message = self:CreatePrefabEntity(drop_prefab, self:GetPosition(inst), number)
            if type(error_message) ~= "nil" then
                print(error_message)
                return error_message
            end
            for _, drop_inst in pairs(drops) do
                drop_inst.Physics:SetVel(0, 5, 0)
            end
            return
        end

        -- 掉落物实体
        if drop_prefab.Transform and drop_prefab.components then
            drop_prefab.Transform:SetPosition(self:GetPosition(inst):Get())
            drop_prefab.Physics:SetVel(0, 5, 0)
            return
        end
    end
end

-- 消耗角色上的物品
function XSTools:ConsumeCharacterItems(prefab, number)
    number = type(number) == "number" and math.floor(number) or 1
    if Prefabs[prefab] then
        GetPlayer().components.inventory:ConsumeByName(prefab, number)
    end
end

-- 可堆叠物品合并, 合并物品, 被合并物品
function XSTools:InstMerge(inst_1, inst_2)
    local res = { inst1 = inst_1, inst2 = inst_2, merge = 0 } -- 合并物品、被合并物品、合并数量
    -- 有物品不存在或不是相同物品
    if not (inst_1 and inst_2) or inst_1.prefab ~= inst_2.prefab then
        return res
    end
    -- 非可堆叠物品
    if not (inst_1.components and inst_1.components.stackable) then
        return res
    end

    -- 合并物品已满
    if inst_1.components.stackable:IsFull() then
        return res
    end

    -- 物品合并
    local can_merged_size = inst_2.components.stackable.stacksize
    inst_1.components.stackable:Put(inst_2, XS:GetPosition(inst_2))

    -- 被合并物品处理
    local residue_size = inst_2.components.stackable.stacksize
    if inst_2:IsValid() then
        inst_2.components.stackable:SetStackSize(residue_size) -- 刷新物品数量
    else
        residue_size = 0
        res.inst2 = nil
    end

    res.merge = can_merged_size - residue_size -- 更新返回结果合并数量

    return res
end

-- 物品入箱, 箱子实体、物品实体、相同物品位置
function XSTools:ItemIntoChest(chest, inst, slots_index)
    -- 实体不存在
    if not chest or not inst or not inst.prefab then
        return false
    end

    -- 箱子不是容器
    if not (chest.components and chest.components.container) then
        return false
    end

    -- 箱子未满
    if not chest.components.container:IsFull() then
        inst.components.inventoryitem:RemoveFromOwner(true) -- 从原库存内摘出
        chest.components.container:GiveItem(inst)           -- 放入箱子
        return true
    end

    -- 不是可堆叠物品
    if not (inst.components and inst.components.stackable) then
        return false
    end

    -- 可堆叠物品
    local into_status = false                      -- 物品入箱结果
    local slots = chest.components.container.slots -- 箱子物品栏
    local exclude_index = {}                       -- 已处理物品栏槽
    -- 已有单独指定位置
    if type(slots_index) == "number" and slots[slots_index] then
        exclude_index[slots_index] = true -- 记录已处理插槽

        local res = self:InstMerge(slots[slots_index], inst)
        if res.merge > 0 and res.inst2 == nil then
            return true -- 物品已全部合并
        end
        if res.merge > 0 then
            into_status = true
        end
    end
    -- 已有多个指定位置
    if type(slots_index) == "table" then
        for _, index in pairs(slots_index) do
            if type(index) == "number" and slots[index] then
                exclude_index[index] = true -- 记录已处理插槽

                local res = self:InstMerge(slots[index], inst)
                if res.merge > 0 and res.inst2 == nil then
                    return true -- 物品已全部合并
                end
                if res.merge > 0 then
                    into_status = true
                end
            end
        end
    end
    -- 物品栏一一处理
    for index, slot_inst in pairs(slots) do
        if slot_inst and not exclude_index[index] then
            local res = self:InstMerge(slot_inst, inst)
            if res.merge > 0 and res.inst2 == nil then
                return true -- 物品已全部合并
            end
            if res.merge > 0 then
                into_status = true
            end
        end
    end
    return into_status
end

-- 身上物品入箱(身上和箱内有相同物品)
function XSTools:SelfStuffsIntoChest(chest)
    local into_chest_status = false          -- 是否有物品入箱
    local can_into_chest_status = false      -- 是否有可入箱物品
    local player = GetPlayer()               -- 玩家角色
    local res = { say = "", status = false } -- 返回结果

    local chest_prefabs = self:GetChestPrefabs(chest)
    if GetTableSize(chest_prefabs) == 0 then
        res.say = "这是一个空的容器不知道放入什么！"
    end

    -- 人物物品栏处理
    local player_slots = player.components.inventory.itemslots --获取人物物品栏
    for _, slot_inst in pairs(player_slots) do
        if HasTableKey(chest_prefabs, slot_inst.prefab) then
            if not can_into_chest_status then
                can_into_chest_status = true
            end
            local into_status = self:ItemIntoChest(chest, slot_inst, chest_prefabs[slot_inst.prefab].indexes)
            if not into_chest_status and into_status then
                into_chest_status = true
            end
        end
    end
    -- 装备包裹处理
    for _, equip_inst in pairs(player.components.inventory.equipslots) do
        if equip_inst and equip_inst.components.container and equip_inst.components.container.type == "pack" then
            local equip_back_slots = equip_inst.components.container.slots
            for _, slot_inst in pairs(equip_back_slots) do
                if HasTableKey(chest_prefabs, slot_inst.prefab) then
                    if not can_into_chest_status then
                        can_into_chest_status = true
                    end
                    local into_status = self:ItemIntoChest(chest, slot_inst, chest_prefabs[slot_inst.prefab].indexes)
                    if not into_chest_status and into_status then
                        into_chest_status = true
                    end
                end
            end
        end
    end

    if not can_into_chest_status then
        res.say = "这个容器没有物品可自动放入！"
    end
    if can_into_chest_status and not into_chest_status then
        res.say = "这个容器已满无法自动放入！"
    end
    if can_into_chest_status and into_chest_status then
        res.say = "一键放入完毕！"
        res.status = true
    end

    return res
end

---- 快速填充物品
--ItemFilling = function(inst, filter_function, fill_function)
--    local fill_status                                                                       --是否填充了物品
--    local owner = inst.components.inventoryitem.owner                                       --填充对象所在父级容器
--    local slots = (owner and owner.components.container and owner.components.container.slots) or
--        (owner and owner.components.inventory and owner.components.inventory.itemslots)     --填充对象所在容器
--    for _, slot in pairs(slots) do
--        if filter_function(slot) then
--            fill_function(inst, slot)     --进行填充
--            slot:Remove()                 --消耗物品
--            fill_status = true
--        end
--    end
--    return fill_status
--end

XS = XSTools()
